﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;

using CSJ2K.Util;
using CSJ2K.Util.IO;

namespace CSJ2K.Jpc
{
    public class CodeBlock
    {
        public CodeStream CodeStream;
        public Tile Tile;
        public TilePart TilePart;
        public Packet Packet;

        public int Layer;
        public int Resolution;
        public int Component;
        public int Precinct;
        public int Subband;

        public long HeaderStart;
        public long DataStart;

        public Point Position;

        public bool CodeBlockIncluded;
        public uint ZeroBitPlanes;
        public byte CodingPasses;
        public byte LBlock = 3;
        public List<uint> CodeBlockLength = new List<uint>();
        public byte[][] CodeBlockBytes;

        public CodeBlock(Packet packet, BitstreamReader bitStream, int subband, uint codeBlocksWide, uint codeBlocksHigh, uint numCodeBlocks, int x, int y)
        {

            CodeStream = packet.CodeStream;
            Tile = packet.Tile;
            TilePart = packet.TilePart;
            Packet = packet;

            Layer = Packet.Layer;
            Resolution = Packet.Resolution;
            Component = Packet.Component;
            Precinct = Packet.Precinct;
            Subband = subband;

            HeaderStart = bitStream.Source.Position;

            Position = new Point(x, y);

            // Create inclusion tag-tree for this packet
            if (Tile.inclusionTree == null)
            {
                Tile.inclusionTree = new TagTree[CodeStream.Components, CodeStream.Resolutions, Tile.Precincts, 4];
                if (DEBUG.TagTree)
                    Debug.WriteLine("New inclusionTree Array");
            }
            if (Tile.inclusionTree[Component, Resolution, Precinct, Subband] == null)
            {
                Tile.inclusionTree[Component, Resolution, Precinct, Subband] = new TagTree((int)codeBlocksWide, (int)codeBlocksHigh);
                if (DEBUG.TagTree)
                    Debug.WriteLine("New inclusionTree");
            }

            // Read tag-tree inclusion information
            CodeBlockIncluded = Tile.inclusionTree[Component, Resolution, Precinct, subband].ReadIncluded(bitStream, x, y, Layer);

            if (CodeBlockIncluded)
            {
                if (DEBUG.Packet) Debug.WriteLine("CodeBlock [" + x + "," + y + "] (" + ((y * codeBlocksWide) + x + 1) + " of " + numCodeBlocks + ") Included");
                if (DEBUG.Packet) Debug.Indent();

                // Create zero-bit-plane tag-tree for this packet
                if (Tile.zeroBitPlaneTree == null) Tile.zeroBitPlaneTree = new TagTree[CodeStream.Components, CodeStream.Resolutions, Tile.Precincts, 4];
                if (Tile.zeroBitPlaneTree[Component, Resolution, Precinct, subband] == null) Tile.zeroBitPlaneTree[Component, Resolution, Precinct, subband] = new TagTree((int)codeBlocksWide, (int)codeBlocksHigh);

                // Read tag-tree zero-bit-plane information
                ZeroBitPlanes = Tile.zeroBitPlaneTree[Component, Resolution, Precinct, subband].ReadValue(bitStream, x, y);

                if (DEBUG.Packet) Debug.WriteLine("ZeroBitPlanes: " + ZeroBitPlanes);

                // read number of coding passes
                if (!bitStream.ReadBit()) CodingPasses = 1;
                else
                {
                    if (!bitStream.ReadBit()) CodingPasses = 2;
                    else
                    {
                        if (!bitStream.ReadBit())
                        {
                            if (!bitStream.ReadBit()) CodingPasses = 3;
                            else CodingPasses = 4;
                        }
                        else
                        {
                            if (!bitStream.ReadBit()) CodingPasses = 5;
                            else
                            {
                                byte buf = bitStream.ReadOctet(5);
                                if (buf != 0x1F) CodingPasses = (byte)(6 + buf); // 6 to 36
                                else
                                {
                                    buf = bitStream.ReadOctet(7);
                                    CodingPasses = (byte)(37 + buf); // 37 to 164
                                }
                            }
                        }
                    }
                }
                if (DEBUG.Packet) Debug.WriteLine("CodingPasses: " + CodingPasses);


                // Create lblock totals
                if (Tile.lBlockDefaults == null)
                {
                    Tile.lBlockDefaults = new byte[CodeStream.Resolutions, CodeStream.Components, 4][,];
                    if (DEBUG.Packet)
                        Debug.WriteLine("New LBlock Defaults Array");
                }

                if (Tile.lBlockDefaults[Resolution, Component, subband] == null)
                {
                    Tile.lBlockDefaults[Resolution, Component, subband] = new byte[codeBlocksWide, codeBlocksHigh];
                    for (int Y = 0; Y < codeBlocksHigh; Y++)
                    {
                        for (int X = 0; X < codeBlocksWide; X++) Tile.lBlockDefaults[Resolution, Component, subband][X, Y] = 3;
                    }
                }
                LBlock = Tile.lBlockDefaults[Resolution, Component, subband][x, y];

                // LBlock addition
                while (bitStream.ReadBit()) LBlock++;
                if (DEBUG.Packet) Debug.WriteLine("LBlock: " + Tile.lBlockDefaults[Resolution, Component, subband][x, y] + "->" + LBlock);
                Tile.lBlockDefaults[Resolution, Component, subband][x, y] = LBlock;

                // TODO: Support Multiple CodeWord?
                {
                    byte bits = (byte)(LBlock + (byte)Math.Floor(Math.Log((double)((int)CodingPasses), 2)));
                    uint codeBlockLength = bitStream.ReadUInt32(bits);
                    CodeBlockLength.Add(codeBlockLength);
                    if (DEBUG.Packet) Debug.WriteLine("CodeBlockLength[0]: " + bits + ": " + codeBlockLength + " bytes");
                }
                if (DEBUG.Packet) Debug.Unindent();
            }
            else if (DEBUG.Packet) Debug.WriteLine("CodeBlock [" + x + "," + y + "] (" + ((y * codeBlocksWide) + x + 1) + " of " + numCodeBlocks + ") Not included");
        }

        public void ReadCodeBlockData(BitstreamReader bitStream)
        {
            if (CodeBlockLength.Count> 0)
            {
                DataStart = bitStream.Source.Position;

                CodeBlockBytes = new byte[CodeBlockLength.Count][];
                for (int mcw = 0; mcw < CodeBlockLength.Count; mcw++)
                {
                    CodeBlockBytes[mcw] = new byte[CodeBlockLength[mcw]];
                    if (DEBUG.Packet) Debug.WriteLine("Reading " + CodeBlockLength[mcw] + " bytes for Subband " + (Subband == 0 ? "LL" : (Subband == 1 ? "HL" : (Subband == 2 ? "LH" : "HH"))) + " CodeBlock [" + Position.X + ", " + Position.Y + "] at " + bitStream.Source.Position);
                    Debug.Indent();
                    
                    bitStream.ReadRawOctets((int)CodeBlockLength[mcw]);
                    
                    Debug.Unindent();
                }
            }
        }
    }
}
